﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace NetAssist.TruthTable
{
  /// <summary>
  /// Represents a Truth Table and provides method for simplification and generating Expressions that solves for the Truth Table conditions.
  /// </summary>
  public class Table
  {
    #region Static Members

    /// <summary>
    /// Initializes an array of 2^0 through 2^15.
    /// </summary>
    private static int[] pow2 = new int[] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 };

    /// <summary>
    /// Fast fetch of values of 2^X.
    /// </summary>
    /// <param name="index">An index ot power of 2 to fetch.</param>
    /// <returns>The value of 2^index.</returns>
    internal static int Pow2(int index)
    {
      return pow2[index];
    }

    #endregion

    #region Declarations

    /// <summary>
    /// Truth table content.
    /// </summary>
    private TruthTableItem[] truthTableItems;
    /// <summary>
    /// Number of inputs used for the truth table.
    /// </summary>
    private int inputs;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes the truth table with boolean values of min terms.
    /// </summary>
    /// <param name="minTerms">An Enumerable&lt;bool&gt; indicating the expected outcome of each min term. </param>
    /// <exception cref="System.ArgumentNullException">
    /// minTerms is null.    
    /// </exception>
    /// <exception cref="System.ArgumentException">
    /// Truth Table is incomplete, the number of min terms supplied is not a power of 2.
    /// </exception>
    public Table(IEnumerable<bool> minTerms)
    {
      Helper.InvalidateNullParam(minTerms, "minTerms");
      int itemCount = minTerms.Count();

      //Calculate and verify number of inputs.
      int inputs = (int)System.Math.Log(itemCount, 2);
      if (pow2[inputs] != itemCount)
        throw new ArgumentException("Truth Table is incomplete", "minTerms");

      this.inputs = inputs;
      int pos = 0;
      List<TruthTableItem> truthTableItemList = new List<TruthTableItem>();
      if (minTerms != null)
        foreach (bool item in minTerms)
        {
          if (item)
            truthTableItemList.Add(new TruthTableItem(pos, inputs));
          pos++;
        }

      this.truthTableItems = truthTableItemList.ToArray();
    }

    #endregion

    #region Properties

    /// <summary>
    /// Number of inputs used for the truth table.
    /// </summary>
    public int Inputs
    {
      get { return inputs; }
    }

    /// <summary>
    /// Gets the truth table item at the specified index of the truth table
    /// </summary>
    /// <param name="index">The zero-based index of the item to locate in the truth table.</param>
    /// <returns>The TruthTableItem at the specified index of the truth table.</returns>
    public TruthTableItem this[int index]
    {
      get { return truthTableItems[index]; }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Simplifies the truth table.
    /// </summary>
    public void Simplify()
    {
      int expressions = truthTableItems.Length;
      List<TruthTableItem> res = new List<TruthTableItem>();
      bool[] simplified = new bool[expressions];

      for (int i = 0; i < expressions - 1; i++)
        for (int j = i + 1; j < expressions; j++)
        {
          TruthTableItem min = TruthTableItem.Simplify(truthTableItems[i], truthTableItems[j]);
          if (min != null)
          {
            simplified[i] = simplified[j] = true;
            res.Add(min);
          }
        }

      for (int i = 0; i < expressions; i++)
        if (!simplified[i])
          res.Add(truthTableItems[i]);

      truthTableItems = res.Distinct().ToArray();

      if (simplified.Any(x => x))
        Simplify();
    }

    /// <summary>
    /// Returns a delegate the will evaluate inputs according to the truth table.
    /// </summary>
    /// <returns>A delegate the will evaluate inputs according to the truth table.</returns>
    public Delegate GetTruthTableDelegate()
    {
      ParameterExpression[] p = GetParameters();
      Expression exp = GetTruthTableExpression(p);
      return Expression.Lambda(exp, p).Compile();
    }

    /// <summary>
    /// Returns an expression tree that represents the logic of the truth table.
    /// </summary>
    /// <returns>An expression tree that represents the logic of the truth table.</returns>
    public Expression GetTruthTableExpression()
    {
      ParameterExpression[] parameterExpressions = GetParameters();
      return GetTruthTableExpression(parameterExpressions);
    }

    /// <summary>
    /// Returns an expression tree that represents the logic of the truth table.
    /// </summary>
    /// <param name="parameterExpressions">An array of ParameterExpressions, one per input.</param>
    /// <returns>An expression tree that represents the logic of the truth table.</returns>
    private Expression GetTruthTableExpression(ParameterExpression[] parameterExpressions)
    {
      Expression rv;
      if (truthTableItems.Any())
        if (truthTableItems.Any(x => x.DontCare))
          rv = Expression.Constant(true); //A Truth Table with a line that all it bits are 'Don't Care' will always evaluate to true.
        else
        {
          //build the first line
          rv = truthTableItems.First().TruthTableItemExpression(parameterExpressions);

          //add subsequent lines using the OrElse expression (equivalent to ||) 
          foreach (TruthTableItem truthTableItem in truthTableItems.Skip(1))
            rv = Expression.OrElse(rv, truthTableItem.TruthTableItemExpression(parameterExpressions));
        }
      else
        rv = Expression.Constant(false); //Empty Truth Table will always evaluate to false.

      return rv;
    }

    /// <summary>
    /// Generates an array of parameter expressions.
    /// </summary>
    /// <returns> An array of System.Linq.Expressions.ParameterExpression.</returns>
    private ParameterExpression[] GetParameters()
    {
      ParameterExpression[] p = new ParameterExpression[inputs];
      Type type = typeof(bool);
      for (int index = 0; index < inputs; index++)
        p[index] = Expression.Parameter(type, GetParamName(index));
      return p;
    }

    /// <summary>
    /// Creates a parameter name with the prefix 'input' and a letter based on its index.
    /// </summary>
    /// <param name="index">A System.Int32 representing the index of the parameter.</param>
    /// <returns>new parameter name.</returns>
    private static string GetParamName(int index)
    {
      return string.Format("input{0}", Char.ConvertFromUtf32(index + 65));
    }

    #endregion

    #region Overrides

    /// <summary>
    /// Returns a string representation of the Truth Table.
    /// </summary>
    /// <returns>A System.String consisting of ab...f and AB...F representing false, true</returns>
    /// <remarks>
    /// 0101 OR 1000 will be return as AbCd || D, 'A' being the most significant bit, 'd' being the least significant bit.
    /// <para>Upper case denotes true and lower case denotes false.</para>
    /// <para>X1X1 will be return as AC. Don't case element are dropped out. </para>
    /// <para>If the truth table is empty '0' is returned, if any of the members is empty (always true) '1' is returned.</para>
    /// </remarks>
    public override string ToString()
    {
      StringBuilder rv = new StringBuilder();

      if (truthTableItems.Any())
        foreach (TruthTableItem item in truthTableItems)
        {
          string exp = item.ToBoolExpression();
          if (string.IsNullOrEmpty(exp))
            return "1";

          rv.Append(" || ");
          rv.Append(item.ToBoolExpression());
        }
      else
        return "0";

      return rv.ToString(4, rv.Length - 4);
    }

    #endregion
  }
}


//public TruthTable(IEnumerable<int> minTerms)
//{
//  int maxItem= minTerms.Max();
//  int inputs = (int)Math.Log(maxItem , 2);
//  if (pow2[inputs] != maxItem)
//    inputs++;

//  this.inputs = inputs;
//  this.truthTableItems = minTerms.Select(T => new TruthTableItem(T, inputs)).ToArray();        
//}